package pp.gateway.application.user.login.api.Impl;

import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import pp.gateway.application.user.login.api.LoginService;
import pp.gateway.application.user.login.dto.LoginByAccountDTO;
import pp.gateway.application.user.login.dto.ParseTokenDTO;
import pp.gateway.application.user.login.param.LoginByAccountParam;
import pp.gateway.application.user.login.param.LogoutParam;
import pp.gateway.application.user.login.param.ParseTokenParam;
import pp.gateway.application.user.login.param.UserRegisterParam;
import pp.gateway.domain.user.aggregation.repository.UserLogin;
import pp.gateway.domain.user.aggregation.repository.UserRepository;
import pp.gateway.domain.user.aggregation.constant.UserLoginConstant;
import pp.infrastructure.exception.PPException;
import pp.infrastructure.exception.PPExceptionInfo;
import pp.infrastructure.mysql.mapper.user.UserWriter;
import pp.infrastructure.redis.RedisHelper;
import pp.infrastructure.util.stringhelper.StringHelper;

import java.util.concurrent.ThreadPoolExecutor;

@Slf4j
@Service
public class LoginServiceImpl implements LoginService {

    @Autowired
    private LoginServiceCheckHelper loginServiceCheckHelper;
    @Autowired
    private UserRepository userLoginRepository;
    @Autowired
    private RedisHelper redisHelper;

    @Autowired
    private UserWriter userWriter;
    @Autowired
    @Qualifier("genericCommonTaskThreadPool")
    private ThreadPoolExecutor commonTaskThreadPool;

    /*
    * 1. 检查 verifyCode 是否正确（由通用模块提供，暂时假设通过）
    * */
    @Override
    public LoginByAccountDTO loginByAccount(LoginByAccountParam input) {
        try {
            LoginByAccountDTO dto = new LoginByAccountDTO();
            // 校验参数
            loginServiceCheckHelper.checkLoginByAccountParam(input);    // 参数校验
            // TODO... 调用远程服务检查 verifyCode
            // 加载领域模型
            UserLogin userLogin = userLoginRepository.loadByAccount(input.getAccount());
            // 验证密码
            boolean result = userLogin.getAccountLoginStrategy().verifyPwd(input.getPwd());
            if(!result) {
                log.error("{}.{} --> {};", this.getClass().getName(), new Object(){}.getClass().getEnclosingMethod().getName()
                        , "账号密码错误，参数 input = " + input);
                throw new PPException(PPExceptionInfo.GENERIC_SYSTEM_INNER_PARAM_INVALID);
            }
            dto.setUserId(userLogin.getUserId());
            dto.setName(userLogin.getAccountLoginStrategy().getName());
            dto.setImg(userLogin.getAccountLoginStrategy().getImg());
            // 生成 token
            String token = StringHelper.generateRandomString(UserLoginConstant.TOKEN_LENGTH, UserLoginConstant.TOKEN_ALIVE_TIME);
            dto.setToken(token);
            // 写入 token
            redisHelper.setCache(token, String.valueOf(userLogin.getUserId()), UserLoginConstant.TOKEN_ALIVE_TIME);
            // 返回 token
            return dto;
        } catch (Exception e) {
            log.error("{}.{} --> {}", this.getClass().getName(), new Object(){}.getClass().getEnclosingMethod().getName()
                    , "登录失败，参数 input = " + input, e);
            throw new PPException(PPExceptionInfo.GENERIC_SYSTEM_FAIL);
        }
    }

    // 简单方法，无须领域实体参与
    @Override
    public ParseTokenDTO parseToken(ParseTokenParam input) {
        try {
            if(input == null || input.getToken() == null || input.getToken().length() != UserLoginConstant.TOKEN_LENGTH) {
                log.error("{}.{} --> {};", this.getClass().getName(), new Object(){}.getClass().getEnclosingMethod().getName()
                        , "token 长度错误，参数 input = " + JSON.toJSONString(input));
                return null;
//                throw new PPException(PPExceptionInfo.GENERIC_INPUT_PARAM_INVALID);
            }
            // 从 Redis 加载 token 对应的缓存值，并检查
            String userId = redisHelper.getCache(input.getToken());
            if(userId == null) {
                log.error("{}.{} --> {};", this.getClass().getName(), new Object(){}.getClass().getEnclosingMethod().getName()
                        , "token 无效，参数 input = " + JSON.toJSONString(input));
                return null;
//                throw new PPException(PPExceptionInfo.GENERIC_INPUT_PARAM_INVALID);
            }
            // 异步刷新 Token 时间
            commonTaskThreadPool.execute(() -> redisHelper.setCache(input.getToken(), userId, UserLoginConstant.TOKEN_ALIVE_TIME));
            // 将用户 id 返回
            ParseTokenDTO dto = new ParseTokenDTO();
            dto.setUserId(Long.parseLong(userId));
            return dto;
        } catch (Exception e) {
            log.error("{}.{} --> {}", this.getClass().getName(), new Object(){}.getClass().getEnclosingMethod().getName()
                    , "解析 token 失败，参数 input = " + JSON.toJSONString(input), e);
            throw new PPException(PPExceptionInfo.GENERIC_SYSTEM_FAIL);
        }
    }

    @Override
    public Boolean logOut(LogoutParam input) {
        try {
            // 校验参数
            loginServiceCheckHelper.checkLogOutParam(input);

            // 从 Redis 中移除 token
            if (!redisHelper.deleteCache(input.getToken())) {
                log.warn("{}.{} --> {}", this.getClass().getName(), new Object(){}.getClass().getEnclosingMethod().getName(),
                        "Redis缓存移除token失败，参数 input = " + JSON.toJSONString(input));
            }
            return true;
        } catch (Exception e) {
            log.error("{}.{} --> {}", this.getClass().getName(),
                    new Object(){}.getClass().getEnclosingMethod().getName(),
                    "退出登录失败，参数 input = " + JSON.toJSONString(input));
            throw new PPException(PPExceptionInfo.GENERIC_SYSTEM_FAIL);
        }
    }

    @Override
    public Boolean UserRegister(UserRegisterParam input) {

        try {
            loginServiceCheckHelper.checkUserRegisterParam(input);
            input.setPwd(StringHelper.encrypt(input.getPwd()));
            if (!userWriter.insert(input.toDo())) {
                log.warn("{}.{} --> {}", this.getClass().getName(), new Object(){}.getClass().getEnclosingMethod().getName(),
                        "用户注册异常 = 参数 input = " + JSON.toJSONString(input));
            }
        } catch (Exception e) {
            log.error("{}.{} --> {}", this.getClass().getName(),
                    new Object(){}.getClass().getEnclosingMethod().getName(),
                    "用户注册失败，参数 input = " + JSON.toJSONString(input));
            throw new PPException(PPExceptionInfo.GENERIC_SYSTEM_FAIL);
        }
        return null;
    }
}
